﻿namespace Database
{
    #region usings

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Xml.Serialization;

    using Common;

    using Model;

    using Xml;

    #endregion

    public class BookEntity
    {
        #region Constants and Fields

        public string Comments;

        public Guid Id;

        public Guid LocationId;

        public string Name;

        public Guid PersonId;

        #endregion

        #region Constructors and Destructors

        public BookEntity()
        {
        }

        public BookEntity(Book book)
        {
            Id = book.Id;
            Name = book.Name;
            Comments = book.Comments;
            LocationId = book.Location.Id;
            PersonId = book.Owner.Id;
        }

        public Book ToBook(DataContext context)
        {
            var book = new Book();
            book.Id = Id;
            book.Name = Name;
            book.Comments = Comments;
            book.Location = context.Locations.Where(l=>l.Id == LocationId).Single();
            book.Owner = context.Persons.Where(p=>p.Id == LocationId).Single();
            return book;
        }

        #endregion
    }

    [XmlRoot("Root")]
    public class XmlStorage
    {
        #region Constants and Fields

        public List<BookEntity> Books;

        public List<Location> Locations;

        public List<Person> Persons;

        private readonly string fileName;

        private DataContext context;

        #endregion

        #region Constructors and Destructors

        public XmlStorage()
        {
        }

        public XmlStorage(DataContext context, string fileName)
        {
            this.context = context;
            this.fileName = fileName;
            Persons = context.Persons.ToList();
            Locations = context.Locations.ToList();
            Books = context.Books.Select(book => new BookEntity(book)).ToList();
        }

        #endregion

        #region Public Methods

        public void Fill()
        {
            string xml = File.ReadAllText(fileName);
            var data = DataManager.ImportData<XmlStorage>(xml);
            context.Locations = data.Locations;
            context.Persons = data.Persons;
            context.Books = data.Books.Select(b => b.ToBook(context)).ToList();
        }

        public void Save()
        {
            DataManager.ExportData(this, fileName);
        }

        #endregion
    }

    // TODO: Replace List by Dictionary
    public class DataContext : IDisposable
    {
        private readonly string fileName;

        private readonly bool readOnly;

        #region Constants and Fields

        private readonly object syncBooks = new object();

        private readonly object syncLocations = new object();

        private readonly object syncPersons = new object();

        private CloneableList<Book> books;

        private CloneableList<Book> editedBooks;

        private CloneableList<Location> editedLocations;

        private CloneableList<Person> editedPersons;

        private CloneableList<Location> locations;

        private CloneableList<Person> persons;

        #endregion

        #region Constructors and Destructors

        public DataContext(string fileName, bool readOnly)
        {
            this.fileName = fileName;
            this.readOnly = readOnly;
            books = new CloneableList<Book>();
            persons = new CloneableList<Person>();
            locations = new CloneableList<Location>();
            if (File.Exists(fileName))
            {
                var storage = new XmlStorage(this, fileName);
                storage.Fill();
            }
        }

        #endregion

        #region Properties

        public List<Book> Books
        {
            get
            {
                if (editedBooks == null)
                {
                    lock (syncBooks)
                    {
                        if (editedBooks == null)
                        {
                            editedBooks = books.Clone();
                        }
                    }
                }

                return editedBooks;
            }

            set
            {
                lock (syncBooks)
                {
                    editedBooks = new CloneableList<Book>(value).Clone();
                }
            }
        }

        public List<Location> Locations
        {
            get
            {
                if (editedLocations == null)
                {
                    lock (syncLocations)
                    {
                        if (editedLocations == null)
                        {
                            editedLocations = locations.Clone();
                        }
                    }
                }

                return editedLocations;
            }

            set
            {
                lock (syncLocations)
                {
                    editedLocations = new CloneableList<Location>(value).Clone();
                }
            }
        }

        public List<Person> Persons
        {
            get
            {
                if (editedPersons == null)
                {
                    lock (syncPersons)
                    {
                        if (editedPersons == null)
                        {
                            editedPersons = persons.Clone();
                        }
                    }
                }

                return editedPersons;
            }

            set
            {
                lock (syncPersons)
                {
                    editedPersons = new CloneableList<Person>(value).Clone();
                }
            }
        }

        #endregion

        #region Public Methods

        public void SubmitChanges()
        {
            CommitAndClear();
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        public void Dispose()
        {
            if (!readOnly)
            {
                var storage = new XmlStorage(this, fileName);
                storage.Save();
            }
            Clear();
        }

        #endregion

        #endregion

        #region Methods

        private void Clear()
        {
            lock (syncBooks)
                lock (syncLocations)
                    lock (syncPersons)
                    {
                        if (editedBooks != null)
                        {
                            editedBooks.Clear();
                            editedBooks = null;
                        }

                        if (editedPersons != null)
                        {
                            editedPersons.Clear();
                            editedPersons = null;
                        }

                        if (editedLocations != null)
                        {
                            editedLocations.Clear();
                            editedLocations = null;
                        }
                    }
        }

        private void CommitAndClear()
        {
            lock (syncBooks)
                lock (syncLocations)
                    lock (syncPersons)
                    {
                        if (editedPersons != null)
                        {
                            foreach (var person in editedPersons.Where(person => person.Id.Equals(Guid.Empty)))
                            {
                                person.Id = Guid.NewGuid();
                            }
                        }

                        if (editedBooks != null)
                        {
                            foreach (var book in editedBooks.Where(book => book.Id.Equals(Guid.Empty)))
                            {
                                book.Id = Guid.NewGuid();
                            }
                        }

                        if (editedLocations != null)
                        {
                            foreach (var location in editedLocations.Where(location => location.Id.Equals(Guid.Empty)))
                            {
                                location.Id = Guid.NewGuid();
                            }
                        }

                        if (editedPersons != null)
                        {
                            persons = editedPersons.Clone();
                        }

                        if (editedLocations != null)
                        {
                            locations = editedLocations.Clone();
                        }

                        if (editedBooks != null)
                        {
                            // TODO: Check constraints
                            books = editedBooks.Clone();
                        }

                        Clear();
                    }
        }

        #endregion
    }
}